using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Windy.Json 
{
    public static class JsonReaderExtension
    {
        public static IEnumerable<JsonNotation> ParseAsJson(this TextReader reader)
        {
            if (reader != null)
            {
                while ((reader.Peek() != -1))
                {
                    //if (!SkipWhiteSpace(reader))
                    //    break;
                    if (char.IsWhiteSpace((char)reader.Peek()))
                    {
                        reader.Read();
                        continue;
                    }

                    switch (reader.Peek())
                    {
                        case '[':
                            reader.Read();
                            yield return JsonNotation.ArrayBegin;
                            break;
                        case ']':
                            reader.Read();
                            yield return JsonNotation.ArrayEnd;
                            break;
                        case '{':
                            reader.Read();
                            yield return JsonNotation.ObjectBegin;
                            break;
                        case '}':
                            reader.Read();
                            yield return JsonNotation.ObjectEnd;
                            break;
                        case ':':
                            reader.Read();
                            yield return JsonNotation.NameSeperator;
                            break;
                        case ',':
                            reader.Read();
                            yield return JsonNotation.MemberSeperator;
                            break;
                        case '"':
                        case '\'':
                            yield return new JsonNotation(ParseString(reader));
                            break;
                        case 't':
                            {
                                string temp = null;
                                if (LookUp(reader, "true", out temp))
                                    yield return JsonNotation.True;
                                else
                                    yield return JsonNotation.CreateUnknown(temp);
                            }
                            break;
                        case 'f':
                            {
                                string temp = null;
                                if (LookUp(reader, "false", out temp))
                                    yield return JsonNotation.False;
                                else
                                    yield return JsonNotation.CreateUnknown(temp);
                            }
                            break;
                        case 'n':
                            {
                                string temp = null;
                                if (LookUp(reader, "null", out temp))
                                    yield return JsonNotation.Null;
                                else
                                    yield return JsonNotation.CreateUnknown(temp);
                            }
                            break;
                        default:
                            {
                                StringBuilder buffer = new StringBuilder();
                                ReadToSeperatorOrEnd(reader, buffer);

                                long integerNumber;
                                double floatPointNumber;
                                decimal bigNumber;
                                string numberText = buffer.ToString();

                                if (long.TryParse(numberText, out integerNumber))
                                {
                                    yield return new JsonNotation(integerNumber);
                                }
                                else if (double.TryParse(numberText, out floatPointNumber))
                                {
                                    yield return new JsonNotation(floatPointNumber);
                                }
                                else if (decimal.TryParse(numberText, out bigNumber))
                                {
                                    yield return new JsonNotation(bigNumber);
                                }
                                else
                                    yield return JsonNotation.CreateUnknown(buffer.ToString());
                            }
                            break;
                    }
                }
            }
        }

        ///// <summary>
        ///// Skip empty char in reader
        ///// White space chars includes: '\r','\n','\t',' '
        ///// </summary>
        ///// <param name="reader">The reader</param>
        ///// <returns>Return false while the reader reach the end otherwise true.</returns>
        //private static bool SkipWhiteSpace(TextReader reader)
        //{
        //    int read = reader.Peek();
        //    if (read == -1) 
        //        return false;

        //    while (char.IsWhiteSpace((char)read))
        //    {
        //        read = reader.Read();
        //        if (read == -1)
        //            return false;
        //    }

        //    return true;
        //}

        public static IEnumerable<JsonNotation> ParseAsJson(this string json)
        {
            return new StringReader(json).ParseAsJson();
        }

        public static IEnumerable<JsonNotation> ParseAsJson(this Stream stream)
        {
            return new StreamReader(stream).ParseAsJson();
        }

        private static string ParseString(TextReader reader)
        {
            char endChar = (char)reader.Read();

            StringBuilder result = new StringBuilder();

            for (int current = reader.Read(); current != endChar || current == -1; current = reader.Read())
            {
                if (current == '\\')
                {
                    switch (reader.Read())
                    {
                        case 'n':
                            result.Append('\n');
                            break;
                        case 'r':
                            result.Append('\r');
                            break;
                        case 't':
                            result.Append('\t');
                            break;
                        case '\'':
                            result.Append('\'');
                            break;
                        case '/':
                            result.Append('/');
                            break;
                        case '"':
                            result.Append('"');
                            break;
                        case '\\':
                            result.Append('\\');
                            break;
                        case 'b':
                            // backspace
                            result.Append('\b');
                            break;
                        case 'u':
                            char[] buffer = new char[4];
                            System.Diagnostics.Debug.Assert(reader.ReadBlock(buffer, 0, 4) == 4);
                            string bufferText = new string(buffer);
                            int charIndex = int.Parse(bufferText, System.Globalization.NumberStyles.HexNumber, System.Globalization.NumberFormatInfo.InvariantInfo);
                            result.Append(Convert.ToChar(charIndex));
                            break;
                        default: // Ignore the transcode 
                            System.Diagnostics.Debug.Fail("ParseString encoutered undefined escape", string.Format("\\{0}", (char)reader.Peek()));
                            result.Append('\\');
                            result.Append((char)reader.Peek());
                            break;
                    }
                }
                else
                    result.Append((char)current);
            }

            return result.ToString();
        }

        private static bool LookUp(TextReader reader, string expected, out string actual)
        {
            bool result = true;
            int fo;
            for (fo = 0; fo < expected.Length; fo++)
            {
                if (reader.Read() != expected[fo])
                {
                    result = false;
                    break;
                }
            }

            if (result)
            {
                actual = expected;
                return true;
            }

            StringBuilder actualBuilder = new StringBuilder();
            actualBuilder.Append(expected, 0, fo - 1);

            ReadToSeperatorOrEnd(reader, actualBuilder);

            actual = actualBuilder.ToString();
            return false;
        }

        private static void ReadToSeperatorOrEnd(TextReader reader, StringBuilder buffer)
        {
            while (true)
            {
                switch (reader.Peek())
                {
                    case -1:
                    case ',':
                    case ':':
                    case ']':
                    case '}':
                        return;
                    default:
                        buffer.Append((char)reader.Read());
                        break;
                }
            }
        }
    }
}
